<!DOCTYPE html><html><head><meta charset="UTF-8" />
<title>with x3.js (inlining)</title>

<script>var begin = Date.now();function say(msg) { console.log(msg + " " + (Date.now() - begin)); }</script>

<script>
var x3;

x3 || (function(global, document) {

// === local vars ===
var _storage = null,    // WebSQL / WebStorage instance
    _lazy    = 0,       // lazy cache revalidation (unit: sec)
    _runs    = 0,       // script.onload / DOMContentLoaded fired count
    _ready   = null,    // x3.arg.callback value
    _revise  = null,    // x3.arg.modifiedCallback value
    _cached  = false,   // x3.arg.callback(cached) value
    IDENT    = "x3",
    EXECUTE_SQL = "executeSql",
    TRANSACTION = "transaction";

// === export ===
global[IDENT] = x3;

x3.clear = function() { // @help: http://code.google.com/p/x3-js/wiki/x3#x3.clear
                        // @desc: clear all data
    _storage.clear();
};

x3.remove = function(url) { // @help: http://code.google.com/p/x3-js/wiki/x3#x3.remove
                            // @desc: remove a data
    _storage.remove(url);
};

x3.reload = function() { // @help: http://code.google.com/p/x3-js/wiki/x3#x3.reload
                         // @desc: reload from server
    location.reload(true);
};

x3.ready = function(path               // @param String(= location.href):
                    /*, var_args */) { // @param URLAndFunctionArray:
                                       //          [<url, callback>, ..., missMatch]
                                       //          url - URLString/RegExp:
                                       //          callback - Function: callback
                                       //          missMatch - Function: miss matched callback
                                       // @help: http://code.google.com/p/x3-js/wiki/x3#x3.ready
                                       // @desc: url dispatcher
    path = path || location + "";

    var ary = [].slice.call(arguments).slice(1), pattern, callback;

    while ((pattern = ary.shift()) && (callback = ary.shift())) {
        if (RegExp(pattern).test(path)) {
            return callback();
        }
    }
    pattern(); // miss match
};

// === implement ===
function x3(url,              // @param URLString:
            callback,         // @param Function: callback(cached:Boolean)
            modifiedCallback, // @param Function(= null): callback(modified:String)
            lazy) {           // @param Number(= 7): delay seconds (unit: sec)
                              // @help: http://code.google.com/p/x3-js/wiki/x3#x3
                              // @desc: JavaScript rapidly reloader

    _lazy = (lazy || 7) * 1000;
    _ready = callback;
    _revise = modifiedCallback;

    try {
        _storage = new WebSQL;
    } catch (err) {
        _storage = new WebStorage;
    }
    _storage.fetch(url, _action);
}

function _action(url,   // @param URLString:
                 date,  // @param String: past date string.
                        //                "Tue, 01 Feb 2000 00:00:00 GMT"
                 src) { // @param String: cached JavaScript expression
    if (src) {
        _eval(src);

        if (_revise) {
            setTimeout(function() {
                _ajax(url, date, function(ok, lastModified, responseText) {
                    if (ok && src !== responseText) { // avoid every status=200
                        _storage.add(url, lastModified, responseText);
                        _revise(lastModified);
                    }
                });
            }, _lazy);
        }
        return _fire(_cached = true);
    }
    _ajax(url, "", function(ok, lastModified, responseText) {
        _eval(responseText);
        _storage.add(url, lastModified, responseText);
        _fire();
    });
}

// inner - ajax request
function _ajax(url,  // @param URLString:
               date, // @param String: If-Modified-Since date string
                     //                "Tue, 01 Feb 2000 00:00:00 GMT"
               fn) { // @param Function: callback(ok, lastModified, responseText)

    var xhr = new XMLHttpRequest;

    xhr.onreadystatechange = function() {
        if (xhr.readyState === 4) {
            switch (xhr.status) {
            case 200:
            case 201:
                return fn(true,
                          xhr.getResponseHeader("Last-Modified") || "",
                          xhr.responseText);
            case 304:
                return fn(false);
            }
        }
    };
    xhr.open("GET", url, true);
    xhr.setRequestHeader("If-Modified-Since",
                         date ? date
                              : "Tue, 01 Feb 2000 00:00:00 GMT");
    xhr.send(null);
}

// inner - eval JavaScript
function _eval(js) { // @param String: JavaScript Expression
    var script = document.createElement("script"),
        head = document.head ||
               document.getElementsByTagName("head")[0];

    head.appendChild(script);
    script.charset = "utf-8";
    script.text = js;
}

// =========================================================

// Class WebSQL
function WebSQL() {
    // [iPhone] LIMIT 5MB
    // [iPhone] Sometimes throw exception in openDatabase
    this.db = global["openDatabase"](IDENT, "1.0", IDENT, 512000);
    this.db[TRANSACTION](function(tr) {
        tr[EXECUTE_SQL]("CREATE TABLE IF NOT EXISTS x3 " +
                        "(url TEXT PRIMARY KEY,date TEXT,src TEXT)");
    });
}

WebSQL.prototype = {
    fetch: function(url,        // @param URLString:
                    callback) { // @param Function: callbac(url, date, src)
                                //          url  - URLString:
                                //          date - String: If-Modified-Since date string
                                //          src  - String: data
                                // @desc: fetch a row
        this.db[TRANSACTION](function(tr) {
            tr[EXECUTE_SQL]("SELECT date,src FROM x3 WHERE url=?",
                            [url], function(tr, r) {

                var date = "", src = "";

                if (r.rows.length) {
                    date = r.rows.item(0).date;
                    src  = r.rows.item(0).src;
                }
                callback(url, date, src);
            });
        });
    },
    add: function(url,   // @param URLString:
                  date,  // @param String: If-Modified-Since date string
                  src) { // @param String: data
                         // @desc: add row

        function _insert(fn) {
            db[TRANSACTION](function(tr) {
                tr[EXECUTE_SQL]("INSERT OR REPLACE INTO x3 " +
                                "VALUES(?,?,?)", [url, date, src]);
            }, function(err) {
                // for Android default browser (except: Chrome for Android beta)
                //
                // err.code = 4
                // err.message = "there was not enough remaining storage space,"
                //               " or the storage quota was reached"
                //               " and the user declined to allow more space"
                fn && fn(true);
            });
        }

        var db = this.db;

        // https://twitter.com/#!/uupaa/statuses/182877455680409600
        _insert(function(retry) {
            retry && setTimeout(function() {
                        _insert();
                    }, 1000);
        });
    },
    remove: function(url) { // @param URLString:
                            // @desc: remove data
        this.db[TRANSACTION](function(tr) {
            tr[EXECUTE_SQL]("DELETE FROM x3 WHERE url=?", [url]);
        });
    },
    clear: function() { // @desc: drop table
        this.db[TRANSACTION](function(tr) {
            tr[EXECUTE_SQL]("DROP TABLE x3");
        });
    }
};

// Class WebStorage(and NoStorage)
function WebStorage() {
    this.db = global["localStorage"] || {};
}

WebStorage.prototype = {
    fetch: function(url, callback) {
        callback(url, this.db[IDENT + url + "date"] || "",
                      this.db[IDENT + url] || "");
    },
    add: function(url, date, src) {
        this.remove(url);
        try {
            this.db[IDENT + url + "date"] = date;
            this.db[IDENT + url] = src;
        } catch (err) { // quota?
            this.remove(url);
        }
    },
    remove: function(url) {
        delete this.db[IDENT + url + "date"];
        delete this.db[IDENT + url];
    },
    clear: function() {
        for (var prop in this.db) {
            prop.indexOf(IDENT) || delete this.db[prop];
        }
    }
};

// =========================================================

// JS Ready callback( script.loaded + DOMContentLoaded )
function _fire() {
    if (++_runs === 2) {
        _ready(_cached);
    }
}

//{@ie678
// DOMContentLoaded for IE. http://d.hatena.ne.jp/uupaa/20100410/1270882150
function _IE_DOMContentLoaded(){
    try {
        (new Image()).doScroll(); // [!] throws
        _fire();
    } catch(err) {
        setTimeout(_IE_DOMContentLoaded, 64); // delay after 64ms
    }
}
//}@ie678

if (document.addEventListener) {
    document.addEventListener("DOMContentLoaded", _fire, false);
} else {
//{@ie678
    _IE_DOMContentLoaded();
//}@ie678
}

})(this, this.document);


    // x3 controller
    x3("dummy.js", function(cached) {
        mm && say("JS Ready: " + (cached ? "cached" : ""));

        // Add your code here
        x3.ready("",
            /x3.debug.htm#a$/, function() { console.log("page a"); },
            /x3.debug.htm#b$/, function() { console.log("page b"); },
            /x3.debug.htm#c$/, function() { console.log("page c"); },
            /x3.debug.htm#d$/, function() { console.log("page d"); },
            function() { console.log("page other"); }
        );

    }, function(modified) { // Modified callback
        alert("App updated! " + modified);
        x3.reload(); // page reload from server
    });

    window.onerror = function(msg, file, errno) {
        console.log(msg + " " + file + " " + errno);
        x3.clear(); // force cache clear
    };

window.addEventListener && window.addEventListener("DOMContentLoaded", function() { say("DOM Ready"); }, false);
window.onload = function() { say("Window Ready"); };
</script>
</head><body>
<button onclick="x3.clear()">clear</button>

<img src="http://x3-js.googlecode.com/svn/trunk/test/img/images0.jpg" />
<img src="http://x3-js.googlecode.com/svn/trunk/test/img/images1.jpg" />
<img src="http://x3-js.googlecode.com/svn/trunk/test/img/images2.jpg" />
<img src="http://x3-js.googlecode.com/svn/trunk/test/img/images3.jpg" />
<img src="http://x3-js.googlecode.com/svn/trunk/test/img/images4.jpg" />
<img src="http://x3-js.googlecode.com/svn/trunk/test/img/images5.jpg" />
<img src="http://x3-js.googlecode.com/svn/trunk/test/img/images6.jpg" />
<img src="http://x3-js.googlecode.com/svn/trunk/test/img/images7.jpg" />
<img src="http://x3-js.googlecode.com/svn/trunk/test/img/images8.jpg" />
<img src="http://x3-js.googlecode.com/svn/trunk/test/img/images9.jpg" />
<!--
<img src="http://x3-js.googlecode.com/svn/trunk/test/img/images10.jpg" />
<img src="http://x3-js.googlecode.com/svn/trunk/test/img/images11.jpg" />
<img src="http://x3-js.googlecode.com/svn/trunk/test/img/images12.jpg" />
<img src="http://x3-js.googlecode.com/svn/trunk/test/img/images13.jpg" />
<img src="http://x3-js.googlecode.com/svn/trunk/test/img/images14.jpg" />
<img src="http://x3-js.googlecode.com/svn/trunk/test/img/images15.jpg" />
<img src="http://x3-js.googlecode.com/svn/trunk/test/img/images16.jpg" />
<img src="http://x3-js.googlecode.com/svn/trunk/test/img/images17.jpg" />
<img src="http://x3-js.googlecode.com/svn/trunk/test/img/images18.jpg" />
<img src="http://x3-js.googlecode.com/svn/trunk/test/img/images19.jpg" />
<img src="http://x3-js.googlecode.com/svn/trunk/test/img/images20.jpg" />
<img src="http://x3-js.googlecode.com/svn/trunk/test/img/images21.jpg" />
<img src="http://x3-js.googlecode.com/svn/trunk/test/img/images22.jpg" />
<img src="http://x3-js.googlecode.com/svn/trunk/test/img/images23.jpg" />
<img src="http://x3-js.googlecode.com/svn/trunk/test/img/images24.jpg" />
<img src="http://x3-js.googlecode.com/svn/trunk/test/img/images25.jpg" />
<img src="http://x3-js.googlecode.com/svn/trunk/test/img/images26.jpg" />
<img src="http://x3-js.googlecode.com/svn/trunk/test/img/images27.jpg" />
<img src="http://x3-js.googlecode.com/svn/trunk/test/img/images28.jpg" />
<img src="http://x3-js.googlecode.com/svn/trunk/test/img/images29.jpg" />
<img src="http://x3-js.googlecode.com/svn/trunk/test/img/images30.jpg" />
<img src="http://x3-js.googlecode.com/svn/trunk/test/img/images31.jpg" />
 -->

</body>
</html>
